package com.sxz.mall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.sxz.mall.product.vo.Catalog2VO;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sxz.mall.common.utils.PageUtils;
import com.sxz.mall.common.utils.Query;

import com.sxz.mall.product.dao.CategoryDao;
import com.sxz.mall.product.entity.CategoryEntity;
import com.sxz.mall.product.service.CategoryService;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> queryPageWithTree(Map<String, Object> params) {

        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        // 遍历出所有的大类 parent_cid=0
        List<CategoryEntity> list = categoryEntities.stream().filter(entity -> entity.getParentCid() == 0).map(entity -> {
            entity.setChildren(getChildren(entity, categoryEntities));
            return entity;
        }).sorted((entity1, entity2) -> {
            int s1 = entity1.getSort() == null ? 0 : entity1.getSort();
            int s2 = entity2.getSort() == null ? 0 : entity2.getSort();
            return s1 - s2;
        }).collect(Collectors.toList());

        return list;
    }

    @Override
    public void removeByCategoryIds(List<Long> list) {
        // 1 todo 检查列表是否在其他业务中

        //2 mybatis做逻辑删除
        baseMapper.deleteBatchIds(list);
    }

    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        List<Long> parentPath = findParentPath(catelogId, paths);
        Collections.reverse(parentPath);
        return parentPath.toArray(new Long[parentPath.size()]);
    }


    // 查询一级分类

    /**
     * 默认的数据和方式需要改进
     * 1、默认key修改
     * 2、默认为永久有效：添加过期时间
     * 3、存储格式为二进制:修改为json格式，  无论什么语言都可以去读写缓存（Python也行）
     *
     * @return
     */
    @Cacheable(value = "catagory", key = "'getLevel1Category'") // 存取缓存,保存到前面两个字段中， 有缓存，不执行该方法，没有，才执行，并存入缓存
//    @CacheEvict(value = "catagory", key = "'getLevel1Category'")  同步删除缓存
    @Override
    public List<CategoryEntity> getLevel1Category() {

        System.out.println("查询了数据库操作.....");
        long start = System.currentTimeMillis();

        List<CategoryEntity> list = baseMapper.queryLevel1Category();

        System.out.println("查询一级分类数据消耗的时间" + (System.currentTimeMillis() - start));
        return list;
    }

    private List<CategoryEntity> queryByParenCid(List<CategoryEntity> list, long parentCid) {
        List<CategoryEntity> entityList = list.stream().filter(item -> item.getParentCid().equals(parentCid))
                .collect(Collectors.toList());
        return entityList;
    }

    // 使用本地缓存
    private Map<String, Map<String, List<Catalog2VO>>> cache = new HashMap<>();


    @Cacheable(value = "catagory", key="'getCatalog2JSON'")
    @Override
    public Map<String, List<Catalog2VO>> getCatalog2JSON() {
        List<CategoryEntity> list = this.list();


//        List<CategoryEntity> level1Category = this.getLevel1Category(); // 访问数据库
        List<CategoryEntity> level1Category = this.queryByParenCid(list, 0l);

        // 数据：一级id对应catalog2Vo的数组
        Map<String, List<Catalog2VO>> map = level1Category.stream()
                .collect(Collectors.toMap(item -> item.getCatId().toString(), item -> {
                    // 根据一级分类查询出二级分类
//            List<CategoryEntity> l2Catalogs = baseMapper.selectList( // 数据库访问
//                    new QueryWrapper<CategoryEntity>()
//                            .eq("parent_cid", item.getCatId()));
                    List<CategoryEntity> l2Catalogs = this.queryByParenCid(list, item.getCatId());

                    List<Catalog2VO> catalog2VOS = null;
                    if (l2Catalogs != null) {
                        catalog2VOS = l2Catalogs.stream().map(l2item -> {
                            Catalog2VO catalog2VO = new Catalog2VO();
                            catalog2VO.setCatalog1Id(l2item.getParentCid().toString());
                            catalog2VO.setId(l2item.getCatId().toString());
                            catalog2VO.setName(l2item.getName());
                            // 查询封装三级分类
//                    List<CategoryEntity> l3catalogs = baseMapper.selectList( // 数据库访问
//                            new QueryWrapper<CategoryEntity>()
//                                    .eq("parent_cid", catalog2VO.getId()));
                            List<CategoryEntity> l3catalogs = this.queryByParenCid(list, l2item.getCatId());

                            if (l3catalogs != null) {
                                List<Catalog2VO.Catalog3VO> catalog3VOS = l3catalogs.stream().map(l3item -> {
                                    Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO();
                                    catalog3VO.setCatalog2Id(l3item.getParentCid().toString());
                                    catalog3VO.setId(l3item.getCatId().toString());
                                    catalog3VO.setName(l3item.getName());
                                    return catalog3VO;
                                }).collect(Collectors.toList());

                                catalog2VO.setCatalog3List(catalog3VOS);
                            }
                            return catalog2VO;
                        }).collect(Collectors.toList());
                    }
                    return catalog2VOS;
                }));
        return map;
    }

    public Map<String, List<Catalog2VO>> getCatalog2JSONWithRedisson() {
        String key = "catalogJSON";
        // 从Redis获取缓存
        String catalogJSON = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isEmpty(catalogJSON)) { // 缓存未命中，查询数据库
            System.out.println("缓存没有命中....");
            Map<String, List<Catalog2VO>> catalog2JSONForDb = getCatalog2JSONForDbWithRedisson();

            return catalog2JSONForDb;
        }

        System.out.println("缓存命中了....");
        // 命中
        Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON,
                new TypeReference<Map<String, List<Catalog2VO>>>() {
                });

        return stringListMap;
    }

    public Map<String, List<Catalog2VO>> getCatalog2JSONForDbWithRedisson() {

        String key1 = "catalogJSON";
        // 加redis分布式锁
        RLock lock = redissonClient.getLock("catalogJson-lock");

        Map<String, List<Catalog2VO>> data = null;
        try {
            lock.lock();
            data = getDataForDb(key1);
        } finally {
            lock.unlock();
        }


        return data;

    }

    public Map<String, List<Catalog2VO>> getCatalog2JSONForDbWithRedisLock() {

        String key1 = "catalogJSON";
        // 加redis分布式锁
        String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().
                setIfAbsent("lock", uuid, 20, TimeUnit.SECONDS);
        if (lock) { // 拿到了锁,直接去获取值, 完成后 释放锁
            // 防止报错，造成死锁，添加过期时间
//            stringRedisTemplate.expire("lock", 20, TimeUnit.SECONDS);
            System.out.println("获取分布式锁成功......");
            Map<String, List<Catalog2VO>> data = null;
            try {
                data = getDataForDb(key1);
            } finally {
                // 查询和删除，保持为原子操作
                String scripts = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end ";
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(scripts, Long.class),
                        Arrays.asList("lock"), uuid);
            }

//            String val = stringRedisTemplate.opsForValue().get("lock");
//            if (uuid.equals(val)) {
//                stringRedisTemplate.delete("lock");
//            }


            return data;
        } else { // 没拿到锁，休眠、重试

            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("获取分布式锁失败......立即重试");

            return getCatalog2JSONForDbWithRedisLock();
        }
    }

    public Map<String, List<Catalog2VO>> getDataForDb(String key1) {

        String catalogJSON = stringRedisTemplate.opsForValue().get(key1);
        if (!StringUtils.isEmpty(catalogJSON)) {
            Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(
                    catalogJSON,
                    new TypeReference<Map<String, List<Catalog2VO>>>() {
                    });
            return stringListMap;
        }

        System.out.println("查询数据库操作...");

        // 3个地方有数据库访问，可以优化
        List<CategoryEntity> list = this.list();


//        List<CategoryEntity> level1Category = this.getLevel1Category(); // 访问数据库
        List<CategoryEntity> level1Category = this.queryByParenCid(list, 0l);

        // 数据：一级id对应catalog2Vo的数组
        Map<String, List<Catalog2VO>> map = level1Category.stream()
                .collect(Collectors.toMap(item -> item.getCatId().toString(), item -> {
                    // 根据一级分类查询出二级分类
//            List<CategoryEntity> l2Catalogs = baseMapper.selectList( // 数据库访问
//                    new QueryWrapper<CategoryEntity>()
//                            .eq("parent_cid", item.getCatId()));
                    List<CategoryEntity> l2Catalogs = this.queryByParenCid(list, item.getCatId());

                    List<Catalog2VO> catalog2VOS = null;
                    if (l2Catalogs != null) {
                        catalog2VOS = l2Catalogs.stream().map(l2item -> {
                            Catalog2VO catalog2VO = new Catalog2VO();
                            catalog2VO.setCatalog1Id(l2item.getParentCid().toString());
                            catalog2VO.setId(l2item.getCatId().toString());
                            catalog2VO.setName(l2item.getName());
                            // 查询封装三级分类
//                    List<CategoryEntity> l3catalogs = baseMapper.selectList( // 数据库访问
//                            new QueryWrapper<CategoryEntity>()
//                                    .eq("parent_cid", catalog2VO.getId()));
                            List<CategoryEntity> l3catalogs = this.queryByParenCid(list, l2item.getCatId());

                            if (l3catalogs != null) {
                                List<Catalog2VO.Catalog3VO> catalog3VOS = l3catalogs.stream().map(l3item -> {
                                    Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO();
                                    catalog3VO.setCatalog2Id(l3item.getParentCid().toString());
                                    catalog3VO.setId(l3item.getCatId().toString());
                                    catalog3VO.setName(l3item.getName());
                                    return catalog3VO;
                                }).collect(Collectors.toList());

                                catalog2VO.setCatalog3List(catalog3VOS);
                            }
                            return catalog2VO;
                        }).collect(Collectors.toList());
                    }
                    return catalog2VOS;
                }));

        // 保存数据到缓存
//        cache.put("getCatalog2JSON", map);
        if (map == null) {
            stringRedisTemplate.opsForValue()
                    .set(key1, "1", 5, TimeUnit.SECONDS);
        } else {
            String jsonString = JSON.toJSONString(map);
            stringRedisTemplate.opsForValue()
                    .set(key1, jsonString, 10, TimeUnit.MINUTES);

        }
        return map;
    }


    // 缓存击穿的时候，加锁， 本地锁
    public synchronized Map<String, List<Catalog2VO>> getCatalog2JSONForDb() {

//        if (cache.containsKey("getCatalog2JSON")) {
//            return cache.get("getCatalog2JSON");
//        }
        // 高并发在此，先访问缓存
        String key1 = "catalogJSON";
        String catalogJSON = stringRedisTemplate.opsForValue().get(key1);
        if (!StringUtils.isEmpty(catalogJSON)) {
            Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(
                    catalogJSON,
                    new TypeReference<Map<String, List<Catalog2VO>>>() {
                    });
            return stringListMap;
        }

        System.out.println("查询数据库操作...");

        // 3个地方有数据库访问，可以优化
        List<CategoryEntity> list = this.list();


//        List<CategoryEntity> level1Category = this.getLevel1Category(); // 访问数据库
        List<CategoryEntity> level1Category = this.queryByParenCid(list, 0l);

        // 数据：一级id对应catalog2Vo的数组
        Map<String, List<Catalog2VO>> map = level1Category.stream()
                .collect(Collectors.toMap(item -> item.getCatId().toString(), item -> {
                    // 根据一级分类查询出二级分类
//            List<CategoryEntity> l2Catalogs = baseMapper.selectList( // 数据库访问
//                    new QueryWrapper<CategoryEntity>()
//                            .eq("parent_cid", item.getCatId()));
                    List<CategoryEntity> l2Catalogs = this.queryByParenCid(list, item.getCatId());

                    List<Catalog2VO> catalog2VOS = null;
                    if (l2Catalogs != null) {
                        catalog2VOS = l2Catalogs.stream().map(l2item -> {
                            Catalog2VO catalog2VO = new Catalog2VO();
                            catalog2VO.setCatalog1Id(l2item.getParentCid().toString());
                            catalog2VO.setId(l2item.getCatId().toString());
                            catalog2VO.setName(l2item.getName());
                            // 查询封装三级分类
//                    List<CategoryEntity> l3catalogs = baseMapper.selectList( // 数据库访问
//                            new QueryWrapper<CategoryEntity>()
//                                    .eq("parent_cid", catalog2VO.getId()));
                            List<CategoryEntity> l3catalogs = this.queryByParenCid(list, l2item.getCatId());

                            if (l3catalogs != null) {
                                List<Catalog2VO.Catalog3VO> catalog3VOS = l3catalogs.stream().map(l3item -> {
                                    Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO();
                                    catalog3VO.setCatalog2Id(l3item.getParentCid().toString());
                                    catalog3VO.setId(l3item.getCatId().toString());
                                    catalog3VO.setName(l3item.getName());
                                    return catalog3VO;
                                }).collect(Collectors.toList());

                                catalog2VO.setCatalog3List(catalog3VOS);
                            }
                            return catalog2VO;
                        }).collect(Collectors.toList());
                    }
                    return catalog2VOS;
                }));

        // 保存数据到缓存
//        cache.put("getCatalog2JSON", map);
        if (map == null) {
            stringRedisTemplate.opsForValue()
                    .set(key1, "1", 5, TimeUnit.SECONDS);
        } else {
            String jsonString = JSON.toJSONString(map);
            stringRedisTemplate.opsForValue()
                    .set(key1, jsonString, 10, TimeUnit.MINUTES);

        }
        return map;
    }

    private List<Long> findParentPath(Long categoryId, List<Long> paths) {
        paths.add(categoryId);
        CategoryEntity entity = this.getById(categoryId);
        if (entity.getParentCid() != 0) {
            findParentPath(entity.getParentCid(), paths);
        }
        return paths;
    }

    // 递归，绑定父类中的子类到children
    private List<CategoryEntity> getChildren(CategoryEntity categoryEntity, List<CategoryEntity> categoryEntities) {
        List<CategoryEntity> list = categoryEntities.stream().filter(entity -> entity.getParentCid().equals(categoryEntity.getCatId())).map(entity -> {
            entity.setChildren(getChildren(entity, categoryEntities));
            return entity;
        }).sorted((entity1, entity2) -> {
            int s1 = entity1.getSort() == null ? 0 : entity1.getSort();
            int s2 = entity2.getSort() == null ? 0 : entity2.getSort();
            return s1 - s2;
        }).collect(Collectors.toList());
        return list;
    }
}